package org.mv.mm.api.data;

import java.io.*;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import org.mv.mm.api.Doc;
import org.mv.mm.api.Fragment;
import org.mv.mm.api.capabilities.*;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.modules.InstalledFileLocator;
import org.openide.util.Lookup;
import org.openide.util.NbPreferences;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
import org.openide.util.lookup.ServiceProvider;

/**
 *
 * @author Martin
 */
@ServiceProvider(service = IDataProvider.class)
public final class SerializeDataProvider implements Lookup.Provider, IDataProvider {

  private static final Logger logger = Logger.getLogger(SerializeDataProvider.class.getName());
  private Lookup lookup;
  private InstanceContent capabilityGroup;
  private Date lastSave;
  private final int saveInterval;

  public SerializeDataProvider() {
    Preferences node = NbPreferences.root();
    saveInterval = node.getInt("minSavingInterval", 60000);
    // Create an InstanceContent to hold abilities...
    capabilityGroup = new InstanceContent();
    // Create an AbstractLookup to expose InstanceContent contents...
    lookup = new AbstractLookup(capabilityGroup);

    addCapabilities();

  }

  @Override
  public Lookup getLookup() {
    return lookup;
  }

  //TODO ! odstranit, nebo upravit, že bude vracet fragmenty bez Doc
  // (ty, co chci použít pro Connection, jako vyřazené po smazání)
  @Override
  public List<Fragment> getFragments(boolean onlyRoot) {
    if (onlyRoot) {
      return Fragment.getFragmentsWithoutParent();
    } else {
      return Fragment.getFragments();
    }
  }

  @Override
  public List<Doc> getDocs() {
    return Doc.getDocs();
  }

  private void loadData() {
    // On first load
    if (lastSave == null) {
      ProgressHandle handle = ProgressHandleFactory.createHandle("Loading saved data...");
      handle.start();

      SerializeContainer crate;
      try {
        //File file = InstalledFileLocator.getDefault().locate("serial.dat", null, false);

        File file = new File(System.getProperty("netbeans.user", "user.home") + "/serial.dat");
        FileInputStream fis = new FileInputStream(file);//TODO soubor
        try (ObjectInputStream in = new ObjectInputStream(fis)) {
          crate = (SerializeContainer) in.readObject();
          Fragment.setFragmentNameMap(crate.getFragments());
          Doc.setDocNameMap(crate.getDocs());
          // Indicate the time of the loaded data
          lastSave = new Date();
          logger.log(Level.INFO, "{0} - Data loaded ({1}) with the class {2} and saving interval {3}", new Object[]{lastSave, file.getAbsolutePath(), crate.getClass(), saveInterval});
        }
      } catch (IOException | NullPointerException ex) {
        logger.log(Level.SEVERE, "No data loaded! There has been an error while attempting to open saved data.", ex);
        lastSave = new Date();
      } catch (ClassNotFoundException ex) {
        logger.log(Level.SEVERE, "No data loaded! You are probably trying to load not compatible data (different structure).", ex);
        lastSave = new Date();
      }

      handle.finish();
    }
  }

  /**
   * Saves the data if the interval between last save (or initial load) is
   * greater then defined interval in options (minSavingInterval).
   *
   * @return true if the save went well.
   */
  @Override
  public boolean persistData(boolean forceSave) {
    if (forceSave || (new Date().getTime() - lastSave.getTime()) > saveInterval) {
      if (!persist()) {

        NotifyDescriptor d = new NotifyDescriptor.Confirmation(
                "Save of data failed!\n\n"
                + "The time of last successful save: "
                + DateFormat.getDateTimeInstance().format(lastSave)// TODo dodat čas
                + "\n Do you want to try again?",
                "Saving failed",
                NotifyDescriptor.YES_NO_OPTION);
        d.setMessageType(NotifyDescriptor.WARNING_MESSAGE);
        while (DialogDisplayer.getDefault().notify(d) == NotifyDescriptor.YES_OPTION) {
          if (persist()) {//TODo vyzkoušet to celé správně s otevřeným souborem
            return true;
          }
        }
        return false;
      } else {
        return true;
      }
    } else {
      return false;
    }

  }

  private boolean persist() {

    SerializeContainer crate = new SerializeContainer(Fragment.getFragmentNameMap(), Doc.getDocNameMap());

    try {
      //File file = InstalledFileLocator.getDefault().locate("serial.dat", null, false);
      File file = new File(System.getProperty("netbeans.user", "user.home") + "/serial.dat");
      FileOutputStream fos = new FileOutputStream(file);//TODO soubor
      try (ObjectOutputStream out = new ObjectOutputStream(fos)) {
        out.writeObject(crate);
        lastSave = new Date();
        logger.log(Level.INFO, "{0} - Data saved ({1}) with the class {2} and saving interval {3}", new Object[]{lastSave, file.getAbsolutePath(), crate.getClass(), saveInterval});
        return true;
      }

    } catch (IOException ex) {
      logger.log(Level.SEVERE, "No data saved! There has been an error while attempting to save data.", ex);
      return false;
    }
  }

  private void addCapabilities() {

    // ******* FRAGMENTS ***********
    capabilityGroup.add(new IReloadableFragmentCapability() {

      @Override
      public void reload() throws Exception {

        //dao = new JPAFragmentDAO();

        loadData();

      }
    });

    capabilityGroup.add(
            new ISaveableFragmentCapability() {

              @Override
              public void save(Fragment fragment) throws Exception {

                // Save the data by serializing.
                persistData(false);
              }
            });

    // ...and a "Creatable" ability to this entity:
    capabilityGroup.add(
            new ICreatableFragmentCapability() {

              @Override
              public Fragment create(Doc doc, String workName, Fragment fragment) throws Exception {

                Fragment frag = Fragment.createFragment(doc, workName, fragment);
                if (frag == null) {
                  return null;
                } else {
                  // Save the data by serializing.
                  persistData(false);
                  return frag;
                }
              }
            });

    capabilityGroup.add(
            new IRemovableFragmentCapability() {

              @Override
              public void remove(Fragment fragmentToDelete) throws Exception {

                // Remove the fragment (this method also removes it from document)
                Fragment.removeFragment(fragmentToDelete);

                persistData(false);
              }
            });


    // ******* DOCS ***********
    capabilityGroup.add(new IReloadableDocCapability() {

      @Override
      public void reload() throws Exception {

        loadData();
      }
    });

    capabilityGroup.add(
            new ISaveableDocCapability() {

              @Override
              public void save(Doc doc) throws Exception {

                // Save the data by serializing.
                persistData(false);
              }
            });

    // ...and a "Creatable" ability to this entity:
    capabilityGroup.add(
            new ICreatableDocCapability() {

              @Override
              public Doc create(String workName) throws Exception {

                Doc doc = Doc.createDoc(workName);
                // Save the data by serializing.
                persistData(false);
                return doc;
              }
            });

    capabilityGroup.add(
            new IRemovableDocCapability() {

              @Override
              public void remove(Doc docToDelete) throws Exception {

                for (Fragment frag : docToDelete.getFragments()) {
                  frag.removeDoc();
                }
                Doc.removeDoc(docToDelete);

                persistData(false);
              }
            });
  }
}
